优化 Unity 渲染
渲染介绍
在最基本的层面上,渲染可以描述如下:
- 中央处理单元,也就是所谓的CPU,负责制定必须绘制的内容以及如何绘制
- CPU向图形处理单元(即GPU)发送指令
- GPU根据CPU的指令画出东西
现在让我们仔细看看会发生什么。我们将在后面的文章中更详细地介绍这些步骤中的每一个步骤,但现在,我们只是熟悉使用的单词,并了解CPU和GPU在渲染中的不同作用
经常用来描述渲染的短语是渲染管道,这是一个很有用的形象,要记住,高效的渲染就是要保持信息的流动
每渲染一帧,CPU就会做以下工作:
- CPU会检查场景中的每个对象,以确定它是否应该被渲染。一个对象只有在满足某些标准的情况下才会被渲染,例如,它的边界框的某些部分必须在摄像机的视野范围内。不会被渲染的对象被称为 "剔除"。有关frustum和frustum culling的更多信息,请参见本页面
- CPU会收集每一个将要渲染的对象的信息,并将这些数据整理成称为绘制调用的命令。一个绘图调用包含了关于单个网格的数据,以及该网格应该如何渲染;例如,应该使用哪些纹理。在某些情况下,共享设置的对象可以合并到同一个绘图调用中。将不同对象的数据合并到同一个绘图调用中称为批处理
- CPU为每个绘图调用创建一个数据包,称为批处理。批次有时可能包含除绘图调用以外的其他数据,但这些情况不太可能导致常见的性能问题,因此我们不会在本文中考虑这些问题
对于每一个包含绘图调用的批次,CPU现在必须做以下工作:
- CPU可以向GPU发送一条命令来改变一些统称为渲染状态的变量。这个命令被称为SetPass调用。SetPass 调用告诉 GPU 使用哪些设置来渲染下一个网格。只有当下一个要渲染的网格需要改变上一个网格的渲染状态时,才会发出SetPass调用
- CPU向GPU发送绘图调用。绘制调用指示GPU使用最近的SetPass调用中定义的设置来渲染指定的网格
- 在某些情况下,批处理可能需要一个以上的pass。一个pass是一段shader代码,一个新的pass需要改变渲染状态。对于批处理中的每一个pass,CPU必须发送一个新的SetPass调用,然后必须再次发送绘制调用
同时,GPU会做以下工作:
- GPU按照发送任务的顺序处理来自CPU的任务
- 如果当前任务是SetPass调用,GPU会更新渲染状态
- 如果当前任务是一个绘制调用,GPU 会渲染网格。这是分阶段进行的,由shader代码的不同部分定义。渲染的这一部分很复杂,我们不会详细介绍,但我们可以了解到,一段称为顶点着色器的代码告诉GPU如何处理网格的顶点,然后一段称为片段着色器的代码告诉GPU如何绘制单个像素
- 这个过程一直重复,直到所有从CPU发送的任务都被GPU处理完为止
渲染问题的类型
关于渲染,最重要的一点是:CPU和GPU都必须完成所有的任务才能渲染画面。如果其中任何一个任务完成的时间过长,都会导致帧的渲染延迟
渲染问题有两种基本原因。第一类问题是由低效的管道引起的。当渲染管道中的一个或多个步骤需要太长的时间才能完成,从而中断了数据的顺利流动时,就会出现低效管道。管道内的低效被称为瓶颈。第二种类型的问题是由简单地试图通过管道推送太多数据引起的。即使是最高效的流水线也有一个极限,它在一帧中能处理多少数据
当我们的游戏因为CPU执行渲染任务的时间过长而导致渲染一帧的时间过长时,我们的游戏就是所谓的CPU绑定。当我们的游戏因为GPU执行渲染任务的时间过长而导致渲染一帧的时间过长时,我们的游戏就是所谓的GPU绑定
如果我们的游戏是和CPU绑定的
大致来说,为了渲染一帧画面,必须由CPU完成的工作分为三类
- 确定必须绘制的内容
- 为GPU准备命令
- 向GPU发送命令
这些大类包含了许多单独的任务,这些任务可以在多个线程中进行。线程允许单独的任务同时发生;当一个线程执行一个任务时,另一个线程可以执行一个完全独立的任务。这意味着可以更快地完成工作。当渲染任务被分割在不同的线程上时,这被称为多线程渲染
在Unity的渲染过程中,有三种线程参与:主线程、渲染线程和工作线程。主线程是我们游戏的大部分CPU任务发生的地方,包括一些渲染任务。渲染线程是一个专门向GPU发送命令的线程。工作线程每个线程都会执行一个任务,比如剔除或网格换肤。哪些任务由哪个线程执行取决于我们游戏的设置和游戏运行的硬件。例如,我们的目标硬件拥有的CPU核数越多,可以催生的工作线程就越多。出于这个原因,在目标硬件上对我们的游戏进行配置是非常重要的;我们的游戏在不同的设备上可能会有非常不同的表现
因为多线程渲染是复杂的,而且依赖于硬件,所以我们在试图提高性能之前,必须了解哪些任务导致我们的游戏被CPU束缚。如果我们的游戏运行得很慢,是因为在一个线程上进行裁剪操作耗时过长,那么我们在另一个线程上减少向GPU发送命令所需的时间是没有帮助的
并不是所有的平台都支持多线程渲染,在撰写本文时,WebGL并不支持这一功能。在不支持多线程渲染的平台上,所有的CPU任务都在同一个线程上执行。如果我们在这样的平台上受到CPU的约束,优化任何CPU工作都会提高CPU性能。如果我们的游戏是这种情况,我们应该阅读下面所有的章节,并考虑哪些优化可能最适合我们的游戏